#ifndef VEC3_H_
#define VEC3_H_

/************************************************************************/
/*  Vector class: Callum Brighting                                      */
/************************************************************************/

#define _USE_MATH_DEFINES
#include <math.h>
#include "Vec2.h"

class Vec3
{
public:

    /* Default Ctr. */
    Vec3() : m_x(0), m_y(0), m_z(0), m_pad(0)
    {}
    
    /* Overloaded Ctr. */
    Vec3(const float x, const float y, const float z) 
        : m_x(x), m_y(y), m_z(z), m_pad(0) {}

    /* Copy Ctr. */
    Vec3(const Vec3& v) 
        : m_x(v.X()), m_y(v.Y()), m_z(v.Z()), m_pad(0) {}

    /* Const Get Functions. */
    float X() const
    {
        return m_x;
    }
    float Y() const
    {
        return m_y;
    }
    float Z() const
    {
        return m_z;
    }
    /* Non Const Get Functions. */
    float& X()
    {
        return m_x;
    }
    float& Y()
    {
        return m_y;
    }
    float& Z()
    {
        return m_z;
    }

    void SetStats(const float x, const float y, const float z)
    {
        m_x = x;
        m_y = y;
        m_z = z;
    }

    /* Adds the given vector to this. */
    Vec3& operator+=(const Vec3& v)
    {
        m_x += v.X();
        m_y += v.Y();
        m_z += v.Z();
        return *this;
    }

    /* Returns the value of the given vector added to this. */
    Vec3 operator+(const Vec3& v) const
    {
        return Vec3(m_x+v.X(), m_y+v.Y(), m_z+v.Z());
    }

    /* Subtracts the given vector from this. */
    void operator-=(const Vec3& v)
    {
        m_x -= v.X();
        m_y -= v.Y();
        m_z -= v.Z();
    }
    
    /* Returns the value of the given vector subtracted from this. */
    Vec3 operator-(const Vec3& v) const
    {
        return Vec3(m_x-v.X(), m_y-v.Y(), m_z-v.Z());
    }

    /* Multiplies this vector by the given scalar. */
    void operator*=(const float value)
    {
        m_x *= value;
        m_y *= value;
        m_z *= value;
    }

    /* Returns a copy of this vector scaled the given value. */
    Vec3 operator*(const float value) const
    {
        return Vec3(m_x*value, m_y*value, m_z*value);
    }

    /* Index operators returning by value or reference. 
    depending on constness */
    float operator[](unsigned i) const
    {
        if (i == 0) return m_x;
        if (i == 1) return m_y;
        return m_z;
    }

    float& operator[](unsigned i) 
    {
        if (i == 0) return m_x;
        if (i == 1) return m_y;
        return m_z;
    }

    /**
     * Calculates and returns the vector product of this vector
     * with the given vector.
     */
    Vec3 CrossProduct(const Vec3& v) const
    {
        return Vec3(m_y * v.Z() - m_z * v.Y(), 
                    m_z * v.X() - m_x * v.Z(), 
                    m_x * v.Y() - m_y * v.X());
    }

    /**
     * Calculates and returns the scalar product of this vector
     * with the given vector.
     */
    float DotProduct(const Vec3& v) const
    {
        return m_x * v.X() + m_y * v.Y() + m_z * v.Z();
    }  
    /**
     * Adds the given vector to this, scaled by the given amount.
     */
    void addScaledVector(const Vec3& v, float scale)
    {
        m_x += v.X() * scale;
        m_y += v.Y() * scale;
        m_z += v.Z() * scale;
    }

    /** Gets the magnitude of this vector. */
    float magnitude() const
    {
        return sqrtf(squareMagnitude());
    }

    /** Gets the squared magnitude of this vector. */
    float squareMagnitude() const
    {
        return (m_x * m_x + 
                m_y * m_y + 
                m_z * m_z );
    }

    /** turns a non-zero vector into a vector of unit length. */
    Vec3& normalise()
    {
        float length = magnitude();
        if (length > 0) 
        {
            (*this) *= 1.0f/length;
        }
        return *this;
    }
    /* returns a copy of this vector that has been normalized. */
    Vec3 getNormalisedVersion() const
    {
        float length = magnitude();
        Vec3 Return(*this);
        if(length > 0)
            Return = ( *this * ((float)1 / length));
        return Return;
    }

    /** Zero all the components of the vector. */
    void clear() 
    { 
        m_x = m_y = m_z = 0;
    }

    /** Flips all the components of the vector. */
    Vec3& invert()
    {
        m_x = -m_x;
        m_y = -m_y;
        m_z = -m_z;
        return *this;
    }

    /* get a copy of this vector that has been inverted */
    Vec3 GetInverted() const
    {
        return Vec3(-m_x, -m_y, -m_z);
    }

    /* Fills an array with x, y ,z */
    void FillArray(float* arr) const
    {
        *arr = m_x; arr++;
        *arr = m_y; arr++;
        *arr = m_z;
    }
public:
    float m_x;
    float m_y;
    float m_z;  

private:
    float m_pad;
};

namespace Vector 
{
    static Vec3 CalculateNormalTri(const Vec3& v1, const Vec3& v2, const Vec3& v3)
    {
        return Vec3((v3 - v1).CrossProduct( (v1 - v2) )).normalise();
    }

    static Vec3 lerp(const Vec3 v1, const Vec3 v2, float interval)
    { 
        if(interval > 1)
            interval = 1;
        return Vec3(v1.X() + interval *(v2.X() - v1.X()),
            v1.Y() + interval *(v2.Y() - v1.Y()),
            v1.Z() + interval *(v2.Z() - v1.Z()));
    }

    static Vec2 lerp(const Vec2 v1, const Vec2 v2, float interval)
    {
        if(interval > 1)
            interval = 1;
        return Vec2(v1.X() + interval *(v2.X() - v1.X()),
            v1.Y() + interval *(v2.Y() - v1.Y()));
    }

    static float lerp(const float f1, const float f2, float interval)
    {
        if(interval > 1)
            interval = 1;
        return (f1 + interval * (f2 - f1));
    }
}//namespace


#endif
